using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;

namespace Nirvana.Common
{
    public static partial class ReflectionExtend
    {
        private static Assembly[] _loadedAssemblies;
        private static Type[] _allTypes;
        private static Dictionary<Type, Type[]> _subTypesMap = new();
        private static Dictionary<Type, string> _typeFriendlyName = new();
        private static Dictionary<Type, string> _typeFriendlyNameCompileSafe = new();
        private static Dictionary<Type, Type[]> _genericArgsTypeCache = new();
        private static Dictionary<MemberInfo, object[]> _memberAttributes = new();
        private static Dictionary<Type, List<FieldInfo>> _cacheType2FieldInfosDic = new();

        private static Assembly[] loadedAssemblies => _loadedAssemblies ??= AppDomain.CurrentDomain.GetAssemblies();
        public static Type[] GetAllTypes(bool includeObsolete)
        {
            if (_allTypes != null)
            {
                return _allTypes;
            }

            var result = new List<Type>();
            foreach (var asm in loadedAssemblies)
            {
                try
                {
                    result.AddRange(asm.GetExportedTypes().Where(t => includeObsolete || !t.NIIsDefined<ObsoleteAttribute>(false)));
                }
                catch
                {
                    continue;
                }
            }

            return _allTypes = result.OrderBy(t => t.Namespace).ThenBy(t => t.FriendlyName()).ToArray();
        }


        public static Type[] NIGetGenericArguments(this Type type)
        {
            if (_genericArgsTypeCache.TryGetValue(type, out var result)) return result;
            return _genericArgsTypeCache[type] = type.GetGenericArguments();
        }

        public static Type[] GetSubClassTypes(this Type baseType)
        {
            if (!_subTypesMap.TryGetValue(baseType, out var result))
            {
                _subTypesMap[baseType] = result = GetAllTypes(false).Where(type => baseType.IsAssignableFrom(type) && !type.IsAbstract && type != baseType).ToArray();
            }

            return result;
        }

        public static object[] GetAllAttributes(this MemberInfo member)
        {
            object[] attributes;
            if (!_memberAttributes.TryGetValue(member, out attributes))
            {
                attributes = member.GetCustomAttributes(true);
                _memberAttributes[member] = attributes;
            }

            return attributes;
        }

        ///<summary>Is attribute defined?</summary>
        public static bool NIIsDefined<T>(this Type type, bool inherited) where T : Attribute
        {
            return type.NIIsDefined(typeof(T), inherited);
        }

        public static bool NIIsDefined(this Type type, Type attributeType, bool inherited)
        {
            return inherited ? type.GetAttribute(attributeType, inherited) != null : type.IsDefined(attributeType, false);
        }

        ///<summary>Get attribute from type of type T</summary>
        public static T GetAttribute<T>(this Type type, bool inherited) where T : Attribute
        {
            return (T) type.GetAttribute(typeof(T), inherited);
        }

        public static Attribute GetAttribute(this Type type, Type attributeType, bool inherited)
        {
            object[] attributes = GetAllAttributes(type);
            if (attributes != null)
            {
                for (var i = 0; i < attributes.Length; i++)
                {
                    var att = (Attribute) attributes[i];
                    var attType = att.GetType();
                    if (attType.NIIsAssignableTo(attributeType))
                    {
                        if (inherited || type.IsDefined(attType, false))
                        {
                            return att;
                        }
                    }
                }
            }

            return null;
        }

        public static T GetAttribute<T>(this MemberInfo info) where T : Attribute
        {
            var attributes = info.GetCustomAttributes();
            var targetType = typeof(T);
            return (from attribute in attributes where targetType.IsInstanceOfType(attribute) select attribute as T).FirstOrDefault();
        }

        public static bool HasAttribute<T>(this MemberInfo info) where T : Attribute
        {
            return info.GetAttribute<T>() != null;
        }

        public static bool TryGetAttribute<T>(this MemberInfo info, out T attribute) where T : Attribute
        {
            attribute = info.GetAttribute<T>();
            return attribute != null;
        }

        public static bool NIIsAssignableTo(this Type type, Type other)
        {
            return other.IsAssignableFrom(type);
        }
    }
}